#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from ... import Mapping
from ..exceptions import UnknownParsingFunctionError
from .stix2converter import (
    ExternalSTIX2Converter, InternalSTIX2Converter, STIX2Converter,
    _MAIN_PARSER_TYPING)
from .stix2mapping import (
    ExternalSTIX2Mapping, InternalSTIX2Mapping, STIX2Mapping)
from abc import ABCMeta
from pymisp import MISPGalaxyCluster
from stix2.v20.sdo import Vulnerability as Vulnerability_v20
from stix2.v21.sdo import Vulnerability as Vulnerability_v21
from typing import Optional, TYPE_CHECKING, Union

if TYPE_CHECKING:
    from ..external_stix2_to_misp import ExternalSTIX2toMISPParser
    from ..internal_stix2_to_misp import InternalSTIX2toMISPParser

_VULNERABILITY_TYPING = Union[
    Vulnerability_v20, Vulnerability_v21
]


class STIX2VulnerabilityMapping(STIX2Mapping, metaclass=ABCMeta):
    __vulnerability_attribute = Mapping(
        **{'type': 'vulnerability', 'object_relation': 'id'}
    )

    @classmethod
    def vulnerability_attribute(cls) -> dict:
        return cls.__vulnerability_attribute


class STIX2VulnerabilityConverter(STIX2Converter, metaclass=ABCMeta):
    def __init__(self, main: _MAIN_PARSER_TYPING):
        self._set_main_parser(main)

    def _create_cluster(self, vulnerability: _VULNERABILITY_TYPING,
                        description: Optional[str] = None,
                        galaxy_type: Optional[str] = None) -> MISPGalaxyCluster:
        vulnerability_args = self._create_cluster_args(
            vulnerability, galaxy_type, description=description
        )
        meta = self._handle_meta_fields(vulnerability)
        if hasattr(vulnerability, 'external_references'):
            meta.update(
                self._handle_external_references(
                    vulnerability.external_references
                )
            )
        if meta:
            vulnerability_args['meta'] = meta
        return self.main_parser._create_misp_galaxy_cluster(
            **vulnerability_args
        )


class ExternalSTIX2VulnerabilityConverter(
        STIX2VulnerabilityConverter, ExternalSTIX2Converter):
    def __init__(self, main: 'ExternalSTIX2toMISPParser'):
        super().__init__(main)
        self._mapping = ExternalSTIX2Mapping

    def parse(self, vulnerability_ref: str):
        vulnerability = self.main_parser._get_stix_object(vulnerability_ref)
        self._parse_galaxy(vulnerability)


class InternalSTIX2VulnerabilityMapping(
        STIX2VulnerabilityMapping, InternalSTIX2Mapping):
    __vulnerability_object_mapping = Mapping(
        description=STIX2Mapping.description_attribute(),
        x_misp_created={'type': 'datetime', 'object_relation': 'created'},
        x_misp_credit={'type': 'text', 'object_relation': 'credit'},
        x_misp_cvss_score={'type': 'float', 'object_relation': 'cvss-score'},
        x_misp_modified={'type': 'datetime', 'object_relation': 'modified'},
        x_misp_published={'type': 'datetime', 'object_relation': 'published'},
        x_misp_state={'type': 'text', 'object_relation': 'state'},
        x_misp_summary=STIX2Mapping.summary_attribute(),
        x_misp_vulnerable_configuration={
            'type': 'cpe',
            'object_relation': 'vulnerable-configuration'
        }
    )

    @classmethod
    def vulnerability_object_mapping(cls) -> dict:
        return cls.__vulnerability_object_mapping


class InternalSTIX2VulnerabilityConverter(
        STIX2VulnerabilityConverter, InternalSTIX2Converter):
    def __init__(self, main: 'InternalSTIX2toMISPParser'):
        super().__init__(main)
        self._mapping = InternalSTIX2VulnerabilityMapping

    def parse(self, vulnerability_ref: str):
        vulnerability = self.main_parser._get_stix_object(vulnerability_ref)
        feature = self._handle_mapping_from_labels(
            vulnerability.labels, vulnerability.id
        )
        try:
            parser = getattr(self, feature)
        except AttributeError:
            raise UnknownParsingFunctionError(feature)
        try:
            parser(vulnerability)
        except Exception as exception:
            _traceback = self.main_parser._parse_traceback(exception)
            self.main_parser._add_error(
                'Error while parsing the Vulnerability object with id '
                f'{vulnerability.id}: {_traceback}'
            )

    def _parse_vulnerability_attribute(
            self, vulnerability: _VULNERABILITY_TYPING):
        attribute = self._create_attribute_dict(vulnerability)
        attribute['value'] = vulnerability.name
        self.main_parser._add_misp_attribute(attribute, vulnerability)

    def _parse_vulnerability_object(
            self, vulnerability: _VULNERABILITY_TYPING):
        misp_object = self._create_misp_object('vulnerability', vulnerability)
        for reference in vulnerability.external_references:
            if reference['source_name'] in ('cve', 'vulnerability'):
                external_id = reference['external_id']
                misp_object.add_attribute(
                    **{
                        'value': external_id,
                        **self._mapping.vulnerability_attribute()
                    }
                )
                if external_id != vulnerability.name:
                    misp_object.add_attribute(
                        **{
                            'value': vulnerability.name,
                            **self._mapping.summary_attribute()
                        }
                    )
            elif reference['source_name'] == 'url':
                misp_object.add_attribute(
                    **{
                        'value': reference['url'],
                        **self._mapping.references_attribute()
                    }
                )
        for attribute in self._generic_parser(vulnerability):
            misp_object.add_attribute(**attribute)
        self.main_parser._add_misp_object(misp_object, vulnerability)
